#include <task.h>
#include <printk.h>

Task *a = (Task *)0x1000; // 固定分配了两个任务，任务内存的区域也给了指定区域，这样认为比较简单
Task *b = (Task *)0x2000; // Task的成员是stack，这里就相当于让stack成员等于0x2000，也就是b=0x2000

extern void TaskSwitch(Task *next);

Task *RunningTask()
{
    asm volatile (
        "movl %esp, %eax\n" // 获取当前esp的值到eax，这部分汇编的作用是为了获取当前任务是哪个
        "andl $0xffff000, %eax\n" // 获得esp的值计算后，eax的值可能是0x1000或0x2000，第一次执行可能是随机值
    ); // 这里结束后，隐藏了返回值，返回值都在eax，执行ret指令后自动带出去，所以调用后可获得当前是哪个人物
}

void Schedule()
{
    Task *curr = RunningTask(); // 准备task，通过返回值获得是0x1000或0x2000
    Task *next = curr == a ? b :a; // 因a=0x1000、b=0x2000，所以在这里实现轮循切换
    TaskSwitch(next); // 任务切换使用汇编实现，这里把下一个任务的入口地址传入了
}

u32 ThreadA()
{
    while (true) {
        printk("_A_");
        Schedule(); // 每次执行完任务主动放弃，主动调用调度函数，切换任务
    }
}

u32 ThreadB()
{
    while (true) {
        printk("#B#");
        Schedule();
    }
}

static void TaskCreate(Task *task, Target target) // Target target等价于等价于 u32 target()，相当于一个函数类型
{
    u32 stack = (u32)task + PAGE_SIZE; // 栈底是页内存+页大小，高地址，向地址增长
    stack -= sizeof(TaskFrame); // 栈的第一块区域是任务栈帧，相当于分配了一块内存给任务栈帧，所以栈增长了sizeof(TaskFrame)
    TaskFrame *frame = (TaskFrame *)stack; // 因TaskFrame中低地址是ebx成员，高地址是eip成员，且对象的起始地址是低地址，正好是当前stack的内存地址
    frame->ebx = 0x11111111; // 给TaskFrame成员赋值，正好对应任务内存布局图中的栈底存EIP，栈顶存EDI
    frame->esi = 0x22222222; // 这里暂时未使用这些寄存器，先只随便赋值
    frame->edi = 0x33333333;
    frame->ebp = 0x44444444;
    frame->eip = (void *)target; // 把任务入口地址存放给EIP，对应栈底的位置
    task->stack = (u32 *)stack; // 这里对应任务内存布局图中的任务内存起始位置，存放的是栈帧当前的地址，也就是栈顶的位置
}

void TaskInit() // 采用最简单的轮循切换的方式创建两个进程/线程，让计算机同时执行两个任务
{
    TaskCreate(a, ThreadA); // 创建任务a进程/线程
    TaskCreate(b, ThreadB); // 创建任务b进程/线程
    Schedule(); // 计算机无限循环调度已有任务，这里就是前面创建好的a和b，但目前还不是抢占调度，是轮循的调度
}